package de.karlNet.sambamanager.ldapController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.naming.directory.SearchControls;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;

import de.karlNet.sambamanager.ldapModel.LdapObject;

public abstract class AbstractNestedTree implements ILdapTree {
	@Autowired
	@Qualifier("ldapTemplate")
	protected LdapTemplate ldapTemplate;
	@Autowired
	protected AttributesMapper attributesMapper;
	@Value("${ldap.baseDN}")
	protected String baseDN;
	private HashMap<String, Set<LdapObject>> cachedTree;
	@Autowired
	private OriginalLdapTree originalLdapTree;

	public AbstractNestedTree() {
		super();
	}

	private List<LdapObject> getAllChildrenOfDN(LdapObject ldapObject,
			List<LdapObject> childrenList, List<String> path) {
		path.add(ldapObject.getDn());

		for (String childrenString : this.getChildrenOfLdapObject(ldapObject)) {
			if (!path.contains(childrenString)) {
				LdapObject childrenLdapObject = this.getObject(childrenString);
				childrenList.add(childrenLdapObject);
				this.addCacheEntry(childrenLdapObject, ldapObject);
				this.getAllChildrenOfDN(childrenLdapObject, childrenList, path);
			}
		}
		path.remove(ldapObject.getDn());
		return childrenList;
	}

	private void addCacheEntry(LdapObject ldapObject, LdapObject parent) {
		String rdn = parent.getDn().toLowerCase();
		if (this.cachedTree.get(rdn) == null) {
			this.cachedTree.put(rdn, new HashSet<LdapObject>());
		}
		this.cachedTree.get(rdn).add(ldapObject);
	}

	@Override
	public List<LdapObject> getAllChildrenOfDN(LdapObject ldapObject) {
		return this.getAllChildrenOfDN(ldapObject.getDn());
	}

	@Override
	public List<LdapObject> getAllChildrenOfDN(String dn) {
		return this.getAllChildrenOfDN(this.getObject(dn),
				new ArrayList<LdapObject>(), new ArrayList<String>());
	}

	@Override
	public List<LdapObject> getChildrenOfDN(String dn) {
		if (this.cachedTree == null) {
			this.cachedTree = new HashMap<String, Set<LdapObject>>();
			this.initCache();
		}
		Set<LdapObject> ldapObjects = this.cachedTree.get(dn.toLowerCase());
		if (ldapObjects == null) {
			return new ArrayList<LdapObject>();
		}
		return new ArrayList<LdapObject>(ldapObjects);
	}

	@PostConstruct
	private void initCache() {
		this.cachedTree = new HashMap<String, Set<LdapObject>>();
		for (LdapObject ldapObject : this.getRootNodes()) {
			this.getAllChildrenOfDN(ldapObject);
		}
	}

	@Override
	public void reset() {
		this.initCache();
	}

	protected abstract List<String> getChildrenOfLdapObject(
			LdapObject ldapObject);

	protected abstract String getInverseAttributeName();

	@Override
	public LdapObject getObject(String dn) {
		return this.originalLdapTree.getObject(dn);
	}

	protected String getObjectClass() {
		return "(objectClass=*)";
	}

	public abstract String getRootNodeFilter();

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<LdapObject> getRootNodes() {
		String filter = this.getRootNodeFilter();
		List ldapObjects = this.ldapTemplate.search(this.baseDN, filter,
				SearchControls.SUBTREE_SCOPE, this.attributesMapper);
		List<LdapObject> ret = new ArrayList<LdapObject>(ldapObjects);
		return ret;
	}
}